
/**************************出于生存和赚钱的要求,在学习计算机网络之前,先学习LwIP的应用********************************/

// HTTP  (hyper text transfer protocol) 有以下不利因素: 1.是一种同步的协议,需要等待服务器的响应才可以进行下一步的工作
//                                                     2.HTTP是单向的,设备只能主动向服务器发出数据,无法被动的接受来自网络的数据,不能用于实时控制
//                                                     3.并且HTTP是重量级的协议
//浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求.WEB服务器根据收到的请求向客户端发送响应信息

//FTP (File Transfer Protocol)是文件传输协议的简称。他也是一个客户端-服务端框架系统。用户通过一个支持FTP协议的客户端程序,连接到在远程主机上的FTP服务端程序
//通过客户端程序向服务程序发出命令,服务端程序执行用户所发出的命令,并将执行的结果返回到客户机(类似于HTTP通讯)

/**
 *  TCP/IP协议的分层
 * 
 *  应用层              -----> DNS HTTP FTP等           应用层以下的工作完成了数据的传递,应用层则决定了你如何应用和处理这些数据,
 *                                                      之所以会有许多的应用层协议,是因为互联网中传递的数据种类很多,差异很大,应用场景十分多样！              
 * 
 *  运输层              -----> 主要使用 TCP,UDP协议      网络层实现了数据包在主机之间的传递(机器之间的传递),而一台机器内部可能运行着多个网络程序
 *                                                      ,传输层可以区分数据包是属于哪一个应用程序的,可以说传输层实现了数据包端到端的传递(程序到程序)
 *                                                      另外数据包在传输过程中可能会出现丢包,乱序和重复现象,网络层并没有提供应对这些错误的机制,但传输
 *                                                      层可以解决这些问题  （每个网络程序都有一个端口）
 * 
 * 
 *  网络层(IP层 IPv4 IPv6)  -----> 主要是IP,ICMP,ARP协议      每台网络设备都有自己的网络地址,网络层规定了主机的网络地址该如何定义,以及如何在网络地址和
 *                                                           MAC地址之间进行映射即ARP协议  (每台网络主机都有一个IP地址)
 * 
 *  
 * 
 *  链路层              -----> MAC层                   规定了数据帧能被网卡接受的条件,最常见的方式是利用网卡的MAC地址,发送方会在欲发送的数据帧的首部
 *                                                     加上接收方网卡的MAC地址信息,接收方只有监听到了属于自己的MAC地址信息后,才会去接收并处理该数据
 * 
 * 
 * 
 * 
 *  物理层              -----> 主要定义物理传输介质     规定了传输信号所需要的物理电平,介质特征                 
 * 
 *   底层协议为相邻的上层协议提供服务,是上层协议得以实现的基础
 */

/**
 *  协议层报文间的封装和拆封
 *  
 *  1.当用户发送数据时,将数据向下交给传输层(TCP/UDP),这是处于应用层的操作,应用层可以通过调用传输层的接口来编写特定的应用程序。而TCP/IP协议一般也会包含
 *    一些简单的应用程序,如Telnet远程登陆,FTP文件传输,SMTP邮件传输协议等
 * 
 *    传输层会在数据前面加上传输层首部(TCP首部或UDP首部),然后向下交给网络层
 * 
 *    网络层会在数据前面加上网络层首部(IP首部),然后将数据交给链路层
 * 
 *    链路层会对数据进行最后一次封装,即在数据前面加上链路层首部(以以太网为例),然后将数据交给网卡
 * 
 *    最后网卡将数据转换成物理链路上的电平信号
 * 
 * 
 *  2.当设备的网卡接收到某个数据包后,会将其放置在网卡的接收缓冲中,并告知TCP/IP内核,然后TCP/IP内核就开始工作了,
 *    它将数据包从接受缓冲中取出,并逐层解析数据包中的协议首部信息,并最终将数据交给某个应用程序。
 * 
 * 
 *                                                                     | 应用数据 |
 *                                                             |APP首部| 应用数据 |      ----->    应用层
 * -----------------------------------------------------------------------------------------------------------
 *                                                      |TCP首部|APP首部| 应用数据|      ----->    传输层
 *                                                           -------TCP段--------
 * -----------------------------------------------------------------------------------------------------------
 *                                               |IP首部|TCP首部|APP首部| 应用数据|      ----->    网络层
 *                                                     ------IP数据报-------
 * -----------------------------------------------------------------------------------------------------------
 *                                     |以太网首部|IP首部|TCP首部|APP首部| 应用数据| CRC |  ----->    链路层
 *                                      ---14---   -20-   -20-                    -4-
 *                                     |------------------以太网帧---------------------|
 *                                               |----------45~1500字节----------|
 * -----------------------------------------------------------------------------------------------------------
 *                                               也就是说APP首部,应用数据加起来长度得有 5~1460
 *         //上面以使用以太网接口为例(以太网首部)
 */

/**
 * LwIP (Light Weight IP) 轻量化的TCP/IP协议
 *        
 * 1.支持ARP协议(以太网地址解析协议)
 * 2.支持ICMP协议(控制报文协议),用于网络的调试与维护
 * 3.支持IGMP协议(互联网组管理协议),可以实现多播数据的接受
 * 4.支持UDP协议(用户数据报协议)
 * 5.支持TCP协议(传输控制协议),包括阻塞控制,RTT估算,快速恢复和快速转发
 * 6.支持PPP协议(点对点通讯协议),支持PPPoE
 * 7.支持DNS(域名解析)
 * 8.支持DHCP协议,动态分配IP地址 (Dynamic Host Configuration Protocol  动态主机配置协议)
 * 9.支持IP协议,包括IPv4,IPv6协议,支持IP分片与重装功能,多网络接口下的数据包转发
 * 10.支持SNMP协议(简单网络管理协议)
 * 11.支持AUTOIP,自动IP地址配置
 * 12.提供专门的内部回调接口(Raw API),用于提高应用程序的性能
 * 13.提供可选择的Socket API,NETCONN API(在多线程的情况下使用)
 */

//重要的东东得进行备份,

/**
 *  LwIP提供了三种编程接口  
 *                  RAW/Callback API                                    NETCONN API                                     SOCKET API
 *  易用性:             低                                                   中                                               高
 *   效率               高                                                   中                                               低
 *  
 *******************************************************************************************************************************************
 *  
 *  RAW/Callback API是指内核回调型的API  在没有操作系统支持的裸机环境中,只能使用这种API进行开发        在有操作系统的环境中,如果使用RAW/Callback API
 *  ,用户的应用程序就以回调函数的形式成为了内核代码的一部分,用户应用程序和内核程序会处于同一个线程中,就省去了任务间通讯和切换的开销
 * 
 *  简单来说,RAW/Callback API的优点有两个:
 *          1.可以在没有操作系统的环境中使用它
 *          2.对比另外两种API,可以提高应用程序的效率,节省内存开销
 *  简单来说,RAW/Callback API的缺点有两个:
 *          1.基于回调函数开发应用程序时的思维过程比较复杂,利用回调函数去实现复杂业务逻辑时,会很麻烦,而且代码的可读性较差
 *          2.在操作系统环境中,应用程序代码与内核代码处于同一个线程中,虽然能够节省任务间通讯和切换任务的开销,但是相应的,应用程序的执行会制约内核
 *            程序的执行,不同应用程序之间也会互相制约。   比如应用程序占用的时间过长,此时碰巧又有大量的数据包到达,由于内核长期得不到执行,网卡接
 *            收缓冲区的数据包就持续积累,到时候就可能因为满载而丢弃一些数据包,造成丢包现象！
 * 
 *******************************************************************************************************************************************
 * 
 *  在操作系统的环境中,可以使用NETCONN API或者Socket API进行网络应用程序的开发  
 *          NETCONN API 是基于操作系统的IPC机制(即信号量和邮箱机制)实现的,它的设计将LwIP内核代码和网络应用程序分离成了独立的线程
 *              LwIP内核就只负责数据包的TCP/IP封装和拆封,而不用进行数据的应用层处理,大大提高了系统对网络数据包的处理效率
 * 
 *  在操作系统环境中,LwIP内核会被实现为一个独立的线程,名为tcpip_thread(这个内核线程优先级最高)
 *        
 * //君子不立于危墙之下！
 * 
 *  简单来说,NETCONN API的优缺点:
 *          1.相较于RAW/Callback API,NETCONN API简化了编程工作,使用户可以按照操作文件的方式来操作网络连接。但是,内核程序和网络应用程序之间的数据
 *            包传递,需要依靠操作系统的信号量和邮箱机制完成,故需要消耗更多的时间和内存,还有加上任务切换的时间开销,效率较低
 * 
 *          2.相较于Socket API,NETCONN API 避免了内核程序和网络应用程序之间的数据拷贝,提高了数据提交的效率。但是,NETCONN API的易用性不如Socket API好
 *            ,他需要用户对LwIP内核所使用数据结构有一定了解   
 * 
 *******************************************************************************************************************************************
 *
 * Socket,即套接字,它对网络连接进行了高级的抽象,使得用户可以像操作文件一样操作网络连接。socket已经成为了网络编程的标准。在不同的系统中,运行着不同的
 * TCP/IP协议,但是只要它实现了Socket的接口,那么用Socket编写的网络应用程序就能在其中运行
 * 不同的系统有自己的一套Socket接口。Windows系统中支持的是WinSock。UNIX/Linux系统中支持的是BSD Socket
 * LwIP中的Socket API是BSD Socket。但是LwIP并没有也没办法实现全部的BSD Socket,所以如果要移植UNIX/Linux系统中的网络应用程序到使用LwIP的系统中,就得注意了！！
 *  
 * 相较于NETCONN API ,Socket API具有更好的易用性。使用Socket API编写的应用程序可读性好,便于维护,也便于移植到其它的系统中。Socket API在内核程序和应用程序
 * 之间存在数据的拷贝,这会降低数据递交的效率   。LwIP的Socket API是基于NETCONN API实现的,所以效率上要较前者(NETCONN API)要打个折扣！
 */

/**
 *              以太网简介 
 *  以太网(Ethernet)是互联网技术的一种，由于它是在组网技术中占的比例最高,许多人直接把以太网理解为互联网
 *  以太网是指遵循IEEE 802.3标准组成的局域网,IEEE 802.3标准规定的主要是位于参考模型的物理层(PHY)和数据链路层中的介质访问控制子层(MAC)
 *  以太网的标志是使用水晶头网线来连接    
 *  IEEE 802.11是无线局域网 WIFI                                尽可能少的被别人干扰！
 *  IEEE 802.15个人域网    蓝牙
 *  IEEE 802.15.4 即ZigBee技术
 *  
 *  IEEE 802.3标准规定了以太网使用的传输介质,传输速度,数据编码方式和冲突检测机制
 *  传输介质包括同轴电缆,双绞线(水晶头网线是一种双绞线),光纤     100BASE-T的网络速度为100Mbps  10BASE-T的网络速度为10Mbps
 */

/**
 *  编码:
 *  为了让接收方在没有外部时钟参考的情况下,也能确定每一位的起始,结束和中间位置,在传输信号时不直接采用二进制编码.
 *  在10BASE-T的传输方式中采用曼切斯特编码,在100BASE-T中则采用4B/5B编码
 *  曼切斯特编码把每一个二进制位的周期分为两个间隔,在表示'1'时,以前半个周期为高电平,后半个周期为低电平。表示'0'则相反。采用曼切斯特编码在每个位周期
 *  都有电压变化,便于同步,但这样编码效率太低
 */

/**
 *  CSMA/CD冲突检测:
 *      早期以太网大多是多个节点连接到同一条网络总线上(总线型网络),存在信道竞争问题,因而每个连接到以太网上的节点都必须具备冲突检测功能。
 *  以太网具备CSMA/CD冲突检测机制,如果多个节点同时利用同一条总线发送数据,则会产生冲突,总线上的节点可通过接受到的信号与原始发送的信号的
 *  比较检测是否存在冲突。若存在冲突则停止发送数据,随机等待一段时间再重传。  
 *      现在大多数局域网组建的时候很少采用总线型网络,大多是一个设备接入到一个独立的路由或交换机接口,组成星型网路,不会产生冲突。但为了兼容
 *  新推出的产品还是带有冲突检测机制。 (交换机隔离冲突域)
 */

/**
 *  MAC子层:
 *    1.MAC的功能
 *          MAC子层是属于数据链路层的下半部分,它主要负责与物理层进行数据交换,如是否可以发送数据,发送数据是否正确,对数据流进行控制等
 *      它自动对来自上层的数据包加上一些控制信号,交给物理层。接收方得到正常数据时,自动去除MAC控制信号,把该数据包交给上层
 *    2.MAC数据包(标准的)
 *          IEEE对以太网上传输的数据包进行了统一规定,该数据包称为MAC数据包
 * 
 *      字节数              字段                            注释
 *     ----------      -----------------       --------------------------------
 *         7              前导字段               被MAC滤掉
 *         1                SFD                 帧起始定界符(被MAC滤掉)
 *------
 *用于 |   6                DA                  目标地址,例如组播,广播或单播地址
 *计算 |   6                SA                            源地址
 *FCS  |   2             类型/长度               数据包的类型或数据包的长度
 *     |
 *     |46-1500          数据+填充               数据包有效负载(和可选的填充字段)
 *------
 *     |   4              FCS                   帧校验序列--CRC
 * 
 * 
 *  
 *     前导字段                 被称为报头,这是一段方波,用于使收发节点的时钟同步,内容为连续7个字节的0x55
 * 
 *     帧起始界定符(SFD)        内容为0xD5 用于区分前导段与数据段
 * 
 *     MAC地址                  MAC地址由48位数字组成,它是网卡的物理地址,在以太网传输的最底层,就是根据MAC地址来收发数据的.部分MAC地址
 *                             用于广播和多播,在同一个网络里不能有两个相同的MAC地址。PC的网卡在出厂时已经设置好了MAC地址,但也可以通过软件来修改
 *                              ,在嵌入式的以太网控制器中可由程序进行配置,数据包中的DA是目标地址,SA是源地址。
 * 
 *     数据包类型               本区域可以用来描述本MAC数据包是属于TCP/IP协议层的IP包，ARP包还是SNMP包，也可以用来描述本MAC数据包数据段的长度。
 *                              如果该值被设置大于0x0600,不用于长度描述,而是用于类型描述功能,表示与以太网帧相关的MAC客户端协议的种类
 *     
 *     数据段                   数据段是MAC包的核心内容,它包含的数据来自MAC的上层。其长度可以从0~1500字节间变化
 * 
 *     填充域                   由于协议要求整个MAC数据包的长度至少为64字节(接收到的数据包如果少于64字节会被认为发生冲突,数据包被自动丢弃)，
 *                              当数据段的字节少于46字节时,在填充域会自动填上无效数据,以使数据包符合长度要求
 *      
 *     检验和域                 MAC数据包的尾部是校验和域,它保存了CRC校验序列,用于检错
 */

/**
 *  STM32的ETH内部自带专用的DMA控制器用于MAC,ETH支持两个工业标准接口 介质独立接口(MII)  简化介质独立接口(RMII) 用于与外部PHY芯片连接
 *  MII和RMII接口用于MAC数据包传输,
 *  ETH集成了站管理接口SMI  用于与外部PHY通讯,用于访问PHY芯片寄存器
 *  
 *  物理层定义了以太网使用的传输介质,传输速度,数据编码方式和冲突检测机制,PHY芯片是物理层功能实现的实体,生活中常用的
 *  水晶头网线+水晶头插座+PHY 组合构成了物理层
 *  
 *  ETH有专门的DMA控制器，它通过AHB主从接口与内核和存储器相连  
 *  AHB从接口接内核,用于访问"控制与状态寄存器"(CSR)空间                                         AHB主接口接存储器，用于控制数据传输
 *  
 *  在进行数据发送时,先将数据由存储器以DMA传输到发送TX FIFO进行缓冲,然后由MAC内核发送
 *  在进行数据接受时,RX FIFO先接受以太网数据帧,再由DMA传输至存储器
 * 
 *          MII接口
 *  MII_TX_CLK
 *  MII_TXD0
 *  MII_TXD1
 *  MII_TXD2
 *  MII_TXD3
 *  MII_TXD_EN
 *  MII_RX_CLK
 *  MII_RXD0
 *  MII_RXD1
 *  MII_RXD2
 *  MII_RXD3
 *  MII_RX_ER
 *  MII_RX_DV
 *  MII_CRS
 *  MII_COL
 * 
 * 
 *         RMII
 *  RMII_TXD0
 *  RMII_TXD1
 *  RMII_TX_EN
 *  RMII_RXD0
 *  RMII_RXD1
 *  RMII_CRS_DV
 *  RMII_REF_CLK
 *  
 * 
 *         SMI
 *  MDIO
 *  MDC
 * 
 *         其他
 *  PPS_OUT   IEEE 1588规定的一个时钟同步机制
 */

/*
  mem.c实现了动态内存池管理机制,什么是【动态内存池】  memp.c实现了静态内存堆管理机制,什么是【静态内存堆】  
  网络数据包在LwIP内核中以pbuf结构体的形式存在  这提高了LwIP内核对数据包处理效率,并且提高了数据包在各层之间递交的效率
  pbuf结构体也是使用RAW/Callback API进行网络应用程序开发的关键  

    UDP可靠性太差,TCP虽然可靠性强,但是很耗费时间和内存 如果需要定义自己的传输层协议,LwIP raw模块可以满足要求

 */

/**
 *****************************************************netif结构体**************************************************
 *  网卡表示网络接口。      LwIP是软件,如何让硬件与软件无缝连接起来?而且网卡又有多种多样,怎样让LwIP使用同样的软件兼容不同的硬件?
 *                          答案就是数据结构 netif              最底层的接口函数必须由用户提供  LwIP提供统一的接口
 *  ethernetif.c 即为底层接口的驱动的模板    用户为自己的网络设备实现驱动时应参照此模板做修改      
 *  简单来说netif是LwIP抽象出来的网卡 LwIP协议栈可以使用多个不同的接口,而ethernetif.c文件提供了netif访问各种不同的网卡,每个网卡有不同的
 *  实现方式,用户只需修改ethernetif.c即可       访问特定的netif网卡使用对应的ethernetif.c文件中的函数
 *  在一个设备中有多个网卡,LwIP会将每个用netif描述的网卡连接成一个链表(单向链表  注意是单向的),该链表就记录每个网卡的netif
 *  netif结构体是移植LwIP的关键
 * 
 *  网络的数据传输:物流       网卡:运输工具           同一个物流公司会有多种运输工具，就需要物流公司去记录这些运输工具，当有一个包裹需要通过
 *  飞机发出去时,物流公司就会将这个包裹通过飞机发送出去。就好比我们的网卡，需要哪个网卡发送或者接收网络数据的时候,就会让对应的网卡去工作。
 *                                                                      谁接收,谁工作
 */

/* struct netif *netif_list //表示网卡链表指针,指向网卡链表的首节点(第一个网卡)
    struct netif *netif_default //表示有多个网卡时,默认使用哪个网卡
*/

//netif 结构体
// struct netif {
//     struct netif *next;
// 指向struct netif链表的下一个

//     ip_addr_t ip_addr;
// 网络中的IP地址,即设备拥有多少个网卡, 就必须有多少个IP地址;

//     ip_addr_t netmask;
// 子网掩码可以用来判断某个IP地址与当前网卡是否处于同一个子网中,IP在发送数据包的时候会选择与目标IP地址处于同一子网的网卡来发送
// 具体工作原理就是使用子网掩码与IP地址进行与操作,将IP地址中的主机号给抹去,剩下的就是该IP地址对应的子网的网络号

//     ip_addr_t gw;  即默认路由地址
// 网关地址在数据包的发送,转发过程中非常重要, 如果要向不属于同一子网的主机(主机目标IP地址与网卡不属于同一子网) 发送一个数据包, 
// 那么 LwIP就会将数据包发送到网关中, 网关设备会对该数据包进行正确的转发.除此之外网关还提供很多高级功能, 如DNS, DHCP等

//     netif_input_fn input;
//input是一个函数指针, 指向一个函数, 该函数由网络设备驱动程序调用, 将数据包传递到TCP / IP协议栈(IP层).对于以太网物理层, 
//通常是ethernet_input()[别和ethernetif_input()搞混了] 参数为pbuf和netif类型, 其中pbuf为接收到的数据包

//     netif_output_fn output;
// output是一个函数指针, 指向一个函数, 该函数由IP层调用, 在接口上发送数据包.用户需要编写该函数并使output指向它, 
// 这个函数的处理步骤是首先 解析硬件地址, 然后发送数据包.对于以太网物理层, 该函数通常是etharp_output(), 参数为pbuf,
// netif 和ip_addr类型, 其中, ipaddr代表要将该数据包发送 到的地址, 但不一定是数据包最终到达的IP地址, 
//如果要发送IP数据包到一个并不在本网络的主机上, 该数据包要被发送到一个路由器上, 这里的ipaddr就是路由器IP地址

//     netif_linkoutput_fn linkoutput;
// linkoutput字段和output类似, 也需要用户自己实现一个函数, 但只有两个参数, 它由ARP模块调用的, 一般是自定义函数low_level_output().
//当需要在网卡上发送一个数据包时,该函数会被ethernet_output函数调用

// #if LWIP_NETIF_STATUS_CALLBACK
//     This function is called when the netif state is set to up or down
//     netif_status_callback_fn status_callback;
// #endif 

// #if LWIP_NETIF_LINK_CALLBACK
//     /** This function is called when the netif link is set to up or down*/
//     netif_status_callback_fn link_callback;
// #endif 

// #if LWIP_NETIF_REMOVE_CALLBACK
//     /** This function is called when the netif has been removed */
//     netif_status_callback_fn remove_callback;
// #endif 

    // /** This field can be set by the device driver and could point 
    //  *  to state information for the device. */
    // void* state;

// #if LWIP_NETIF_HOSTNAME
//     /* the hostname for this netif, NULL is a valid value */
//     const char* hostname;
// #endif /* LWIP_NETIF_HOSTNAME */

// #if LWIP_CHECKSUM_CTRL_PER_NETIF
//     u16_t chksum_flags;
// #endif 

//     u16_t mtu;
// IP层就是网络层 最大传输单位(以字节为单位), 对于以太网一般为1500, 在IP层发送数据的时候, 
// LwIP会使用该字段决定是否需要对数据包进行分片处理。为毛是在IP层进行分片处理 ? 因为链路层(MAC层) 不提供任何的差错处理机制, 
// 如果在网卡中接收的数据包不满足网卡自身的属性，那么网卡可能就会直接丢弃该数据包, 也可能在底层进行分包发送,
// 但这分包在IP层看来是不可接受的, 因为它打乱了数据的结构, 所以只能由IP层进行分片处理 !(各司其职！！)

//     u8_t hwaddr[6];
// 此网卡的链路层硬件地址

//     u8_t hwaddr_len;
// 硬件地址长度, 对于以太网就是MAC地址长度, 为6字节

//     u8_t flags;
// 网卡状态信息标志位, 是很重要的控制字段, 包括网卡功能使能, 广播使能, ARP使能等等重要控制位

//     char name[2];
// 用于保存每一个网卡的名字,网卡名字用于标示网卡硬件的种类比如蓝牙设备的网卡名字可以是bt
// WLAN设备的名字是wl,当然网卡名字可以随便设置。当有两个网卡有相同的名字时可以使用下面的
// num字段来区分相同类型的不同网卡

//     u8_t num;    //solicit 恳求
// #if MIB2_STATS
//   u8_t link_type;
// 连接类型
//   u32_t link_speed;
// 连接速度
//   u32_t ts;
// 最后一次改变的时间戳
//     struct stats_mib2_netif_ctrs mib2_counters;
// 计数器
// #endif  /* MIB2_STATS */

// #if LWIP_IPV4 && LWIP_IGMP
//   /** 调用这个函数可以用来添加或者删除多播以太网MAC的过滤表的入口.*/
//   netif_igmp_mac_filter_fn igmp_mac_filter;
// #endif /* LWIP_IPV4 && LWIP_IGMP */

// #if LWIP_NETIF_USE_HINTS
//   struct netif_hint *hints;
// #endif /* LWIP_NETIF_USE_HINTS */

// #if ENABLE_LOOPBACK
//   /* 待排队的数据包的列表 */
//   struct pbuf *loop_first;   caallback回调  loopback回环
//   struct pbuf *loop_last;
// #if LWIP_LOOPBACK_MAX_PBUFS
//   u16_t loop_cnt_current;
// #endif /* LWIP_LOOPBACK_MAX_PBUFS */
// #endif /* ENABLE_LOOPBACK *
    // }


// netif_add() 函数就是将网卡挂载到netif_list链表上的    if=interface

/**
netif_add(struct netif* netif, 
          const ip4_addr_t* ipaddr,   //指针可变,指针指向的内容不可变
          const ip4_addr_t* netmask, 
          const ip4_addr_t* gw, 
          void* state, 
          netif_init_fn init, 
          netif_input_fn input)
{
    if (ipaddr == NULL) {               //当ipaddr,netmask,gw指向为NULL时
        ipaddr = ip_2_ip4(IP4_ADDR_ANY);//是一个指针,指向的变量为0
    }
    if (netmask == NULL) {
        netmask = ip_2_ip4(IP4_ADDR_ANY);
    }
    if (gw == NULL) {
        gw = ip_2_ip4(IP4_ADDR_ANY);
    }
    //reset new interface configuration state
    ip_addr_set_zero_ip4(&netif->ip_addr); //(&netif->ip_addr)是一个结构体指针
    ip_addr_set_zero_ip4(&netif->netmask); //(&netif->ip_addr)->addr=0;
    ip_addr_set_zero_ip4(&netif->gw);
    netif->output = netif_null_output_ip4;  

    NETIF_SET_CHECKSUM_CTRL(netif, NETIF_CHECKSUM_ENABLE_ALL);
    netif->mtu = 0;
    netif->flags = 0;

    #ifdef netif_get_client_data   //client_data[]是一个 void * (即空指针类型的数组)
    memset(netif->client_data, 0, sizeof(netif->client_data));
    #endif 

    //remember netif specific state information data
    netif->state = state;   //void * 类型的指针
    netif->num = netif_num; //用来区分同种类型的不同网卡
    netif->input = input;   //指向ethernet_input
    NETIF_RESET_HINTS(netif); //等同于(netif)->hints=NULL;

    netif_set_addr(netif, ipaddr, netmask, gw);
    // call user specified initialization function for netif
    if (init(netif) != ERR_OK) {  //其实就是调用ethernetif_init(当网卡是以太网时)
        return NULL;
    }

#if !LWIP_SINGLE_NETIF   1:表示只是用一个网卡  0:表示使用多个网卡
    //  Assign a unique netif number in the range [0..254], so that (num+1) can
    //  serve as an interface index that fits in a u8_t.
    //  We assume that the new netif has not yet been added to the list here.
    //  This algorithm is O(n^2), but that should be OK for lwIP.
  {
    struct netif *netif2;
    int num_netifs;
    do {
      if (netif->num == 255) { //0-254  表示255个网卡
        netif->num = 0;
      }
      num_netifs = 0;
      for (netif2 = netif_list; netif2 != NULL; netif2 = netif2->next) {
        LWIP_ASSERT("netif already added", netif2 != netif); //指向同一个地址,表明网卡已经添加了
        num_netifs++;
        LWIP_ASSERT("too many netifs, max. supported number is 255", num_netifs <= 255);
        if (netif2->num == netif->num) {
          netif->num++;//应该是初次使用为零时,会执行   一般情况下netif->num=netif2->num+1
          break;
        }  //netif_list链表中的元素的num依次递减
      }
    } while (netif2 != NULL); //当netif链表中有n个元素时其时间复杂度n*(n+1)/2 即O(n^2)
  }
  if (netif->num == 254) {
    netif_num = 0; //方便下一次使用
  } else {
    netif_num = (u8_t)(netif->num + 1); //方便下一次使用   
  }
  //add this netif to the list
  netif->next = netif_list; //新加的节点都在前面,并且 netif_list也随时向前移动
  netif_list = netif;      //单向链表的头插法
#endif //"LWIP_SINGLE_NETIF
  mib2_netif_added(netif);
  
} */

//这个应该属于netif_set_addr中的东东
    // #define IPADDR_ANY                   ((u32_t)0x00000000UL)
    // #define ip4_addr_isany_val(addr1)    ((addr1).addr == IPADDR_ANY)
    // #define ip4_addr_isany(addr1)        ((addr1) == NULL || ip4_addr_isany_val(*(addr1)))

//   remove = ip4_addr_isany(ipaddr);  //
//   if (remove) {  //ipaddr==NULL 或者 ipaddr指向的元素值为0
//        when removing an address, we have to remove it *before* changing netmask/gw
//        to ensure that tcp RST segment can be sent correctly
//     if (netif_do_set_ipaddr(netif, ipaddr, &old_addr)) { //ipaddr是一个结构体指针
// #if LWIP_NETIF_EXT_STATUS_CALLBACK                       //old_addr是一个结构体
//       change_reason |= LWIP_NSC_IPV4_ADDRESS_CHANGED;
//       cb_args.ipv4_changed.old_address = &old_addr;
// #endif
//     }           //先不细究,先整体过一遍,然后需要深入研究时再细究,一遍是不够的


struct ethernetif {
  struct eth_addr* ethaddr;
  /* Add whatever per-interface state that is needed here. */
};  //ethaddr即MAC地址是唯一不可或缺的,会通过netif的state成员变量将MAC信息提交给上层


//MAC地址是一样的
//IPv6 : IP地址有16Bytes
//IPv4 : IP地址有4 Bytes

//现在是别人带着你学,后面就得自己开始找食物了!!!  下次写文档时得使用makedown来进行了   抓住战机,千万别贻误战机

/****************************LwIP的内存管理***********************************/
// 内存分配的本质 : 就是事先准备一大块内存堆(动态内存分配是在堆中,可以理解为一个巨大的数组),然后将该空间起始地址返回给申请者。

    // 1. （1）固定大小的内存块 单链表 将一些大小相同的内存块,通过单链表连接起来 !使用这种分配策略,用户只能申请大小固定的内存块
    //     (优点: 分配内存的时间是固定的, 非常高效;缺点: 只能申请固定大小的内存块)。这种分配策略在LwIP中被称之为动态内存池分配策略 !
    //     既然不好为毛要使用呢?这是因为LwIP中有很多固定的数据结构,如TCP首部,UDP首部,IP首部,以太网首部等都是固定的数据结构
    //    
    //    （2）可变长度分配RT-Thread中使用的内存分配策略！  以时间换空间  但会造成内存碎片(如果没有及时处理这些碎片的话)
    //      内存碎片导致系统崩溃的原因并不是因为系统没有可用内存了,而是内存块被分割成了很多不连续的小内存块,系统又没有办法
    //      将这些碎片连接起来,这样就会导致申请失败！

//内存池的预处理
    //动态内存池(POOL) 内核初始化时,会事先在内存中初始化相应的内存池,内核会将所有可用的区域根据宏定义的配置以 固定的大小(重点) 
    //为单位进行划分,然后用一个简单的链表将所有空闲块连接起来,这样组成了一个个的内存块(池).链表中所有的节点的大小相同,分配时不需要查找,
    //直接取出第一个节点中的空间分配给用户即可

    //LWIP_MEMPOOL(UDP_PCB, MEMP_NUM_UDP_PCB, sizeof(struct udp_pcb), "UDP_PCB") 
    //MEMP_NUM_UDP_PCB块(内存池)的数目  每个块(内存池)的大小:sizeof(struct udp_pcb)
    // memp_std.h使用方式的例子
    // typedef enum {
    // #define LWIP_MEMPOOL(name, num, size, desc) MEMP_##name, //用后方的代替前方的##连接符,用来将两个Token连接为一个Token,Token可以是C语言关健字,也可以是用户自定义的变量
    // #include "lwip/priv/memp_std.h"                          //这个东西,受上面MEMP_##name的影响,注意 MEMP_##name,这里有一个,号
    //    MEMP_MAX
    //  } memp_t;
    // 注意这里使用的是enum

    //上面的##被称为连接符(concatenator) 在编译过程中遇到了 "LWIP_MEMPOOL(EmbedFire,num,size,desc)"这句代码, 编译器就会将它替换为 "MEMP_EmbedFire,"
    //上面的代码经过编译器处理后,
    //就变成了下面的样子
    //typedef enum {
    //MEMP_RAW_PCB,
    //MEMP_UDP_PCB,
    //...... MEMP_MAX
    //} memp_t;
    // memp_t类型在整个内存池的管理中是最重要的存在, 通过内存池申请函数申请内存的时候, 唯一的参数就是memp_t类型的, 它将告诉分配的函数在哪种类型的POOL中 
    //去分配对应的内存块, 这样就管理了系统中所有类型的POOL

    // memp_std.h头文件的最后,用的这些#undef很重要
    // #undef LWIP_MEMPOOL
    // #undef LWIP_MALLOC_MEMPOOL
    // #undef LWIP_MALLOC_MEMPOOL_START
    // #undef LWIP_MALLOC_MEMPOOL_END
    // #undef LWIP_PBUF_MEMPOOL
    //在memp_std.h文件的最后需要对LWIP_MEMPOOL宏定义进行撤销,
    //因为memp_std.h会被多个地方调用, 每个调用的地方会重新定义这个宏定义的功能
    //#undef LWIP_MEMPOOL 很重要 针对 #define LWIP_MEMPOOL 也可以针对 #define LWIP_MEMPOOL(XXXXXX)

    // const struct memp_desc* const memp_pools[MEMP_MAX]
    // = {
           //和上面的东东进行对比  里面的元素都是指向struct memp_desc的指针
    //     #define LWIP_MEMPOOL(name, num, size, desc) &memp_##name,
    //     #include "lwip/priv/memp_std.h"
    //   };
`
    // 后面看LwIP源码的时候,发现找不到定义, 但是编译没有问题, 那可能就是通过 "##"连接符产生的 
    // 关键是在 #define LWIP_MEMPOOL(name, num, size, desc) MEMP_##name, 上面作文章

    /*********************************************************************************/
    /*********************************************************************************/
    // #if !defined MEMP_NUM_RAW_PCB || defined __DOXYGEN__
    // #define MEMP_NUM_RAW_PCB                4
    // #endif

    // #define MEM_ALIGNMENT           4

    // #ifndef LWIP_MEM_ALIGN_SIZE    //目的是让其4字节对齐
    // #define LWIP_MEM_ALIGN_SIZE(size) (((size) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT-1U))
    // #endif

    // #define MEMP_SIZE           0
    // #define MEMP_ALIGN_SIZE(x) (LWIP_MEM_ALIGN_SIZE(x))

    // #if MEMP_STATS
    // #define LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(name) static struct stats_mem name;
    // #define LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(name) &name,
    // #else
    // #define LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(name)
    // #define LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(name)
    // #endif

    // #if defined(LWIP_DEBUG) || MEMP_OVERFLOW_CHECK || LWIP_STATS_DISPLAY
    // #define DECLARE_LWIP_MEMPOOL_DESC(desc) (desc),
    // #else
    // #define DECLARE_LWIP_MEMPOOL_DESC(desc)
    // #endif

    // #if !MEMP_MEM_MALLOC || MEMP_OVERFLOW_CHECK
    // struct memp {
    //   struct memp *next;
    // #if MEMP_OVERFLOW_CHECK
    //   const char *file;
    //   int line;
    // #endif /* MEMP_OVERFLOW_CHECK */
    // };
    // #endif /* !MEMP_MEM_MALLOC || MEMP_OVERFLOW_CHECK */

    // /** Memory pool descriptor */
    // struct memp_desc {
    // #if defined(LWIP_DEBUG) || MEMP_OVERFLOW_CHECK || LWIP_STATS_DISPLAY
    //   /** Textual description */
    //   const char *desc;
    // #endif /* LWIP_DEBUG || MEMP_OVERFLOW_CHECK || LWIP_STATS_DISPLAY */
    // #if MEMP_STATS
    //   /** Statistics */
    //   struct stats_mem *stats;
    // #endif

    //   /** Element size */
    //   u16_t size;

    // #if !MEMP_MEM_MALLOC
    //   /** Number of elements */
    //   u16_t num;

    //   /** Base address */
    //   u8_t *base;

    //   /** First free element of each pool. Elements form a linked list. */
    //   struct memp **tab;
    // #endif /* MEMP_MEM_MALLOC */
    // };

    // #if MEMP_STATS
    // #define LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(name) static struct stats_mem name;
    // #define LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(name) &name,
    // #else
    // #define LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(name)
    // #define LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(name)
    // #endif

    // #ifndef LWIP_MEM_ALIGN_BUFFER
    // #define LWIP_MEM_ALIGN_BUFFER(size) (((size) + MEM_ALIGNMENT - 1U))
    // #endif

    // #ifndef LWIP_DECLARE_MEMORY_ALIGNED
    // #define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) u8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)]
    // #endi

    // #define LWIP_MEMPOOL_DECLARE(name,num,size,desc) \
    //   LWIP_DECLARE_MEMORY_ALIGNED(memp_memory_ ## name ## _base, ((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size)))); \
    //     \
    //   LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(memp_stats_ ## name) \
    //     \
    //   static struct memp *memp_tab_ ## name; \
    //     \
    //   const struct memp_desc memp_ ## name = { \
    //     DECLARE_LWIP_MEMPOOL_DESC(desc) \
    //     LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(memp_stats_ ## name) \
    //     LWIP_MEM_ALIGN_SIZE(size), \
    //     (num), \
    //     memp_memory_ ## name ## _base, \
    //     &memp_tab_ ## name \
    //   };
    /**********************************************************************************************************/
    /**********************************************************************************************************/

    //下面是在memp.c中定义的
    // #define LWIP_MEMPOOL(name, num, size, desc) LWIP_MEMPOOL_DECLARE(name, num, size, desc)
    // #include "lwip/priv/memp_std.h"
    // 注意这里的东东很重要!!!!!!!!!!!  上面是定义了memp_##name结构体变量,下面是使用它们
    // const struct memp_desc *const memp_pools[MEMP_MAX]= {
    // #define LWIP_MEMPOOL(name, num, size, desc) &memp_##name,
    // #include "lwip/priv/memp_std.h"
    // };

    // 上面相当于如下 "lwip/priv/memp_std.h"中只选取一段,就取 LWIP_RAW这一段,举个例子

    // #define LWIP_MEMPOOL(name, num, size, desc) LWIP_MEMPOOL_DECLARE(name, num, size, desc)
    // LWIP_MEMPOOL(RAW_PCB, MEMP_NUM_RAW_PCB, sizeof(struct raw_pcb), "RAW_PCB")
    // 上面产生了 const struct memp_desc 类型的结构体变量(注意是变量)
    // /**********************************************************************************************************/
    // 上面就会变成下面的东东 LWIP_MEMPOOL_DECLARE(RAW_PCB, MEMP_NUM_RAW_PCB, sizeof(struct raw_pcb), "RAW_PCB")
    // /*通过转换后得到的结果,例子是RAW_PCB      当##前后有空格时,也没关系,空格会被忽略*/
    // LWIP_DECLARE_MEMORY_ALIGNED(memp_memory_RAW_PCB_base, ((MEMP_NUM_RAW_PCB) * (MEMP_SIZE + MEMP_ALIGN_SIZE(sizeof(struct raw_pcb)))));
    // LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(memp_stats_RAW_PCB);
    // static struct memp* memp_tab_RAW_PCB;
    // const struct memp_desc memp_RAW_PCB = { //这里就是const struct memp_desc 类型的结构体变量
    //     DECLARE_LWIP_MEMPOOL_DESC("RAW_PCB")
    //     LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(memp_stats_RAW_PCB)
    //     LWIP_MEM_ALIGN_SIZE(sizeof(struct raw_pcb)),
    //     (MEMP_NUM_RAW_PCB),
    //     memp_memory_RAW_PCB_base,
    //     &memp_tab_RAW_PCB
    // };

    /*****************再次转换*****得一步一步来***********************/
    // u8_t memp_memory_RAW_PCB_base[(((((MEMP_NUM_RAW_PCB) * (MEMP_SIZE + MEMP_ALIGN_SIZE(sizeof(struct raw_pcb))) )) + MEM_ALIGNMENT - 1U))]; 
    // static struct stats_mem memp_stats_RAW_PCB                      
    // static struct memp* memp_tab_RAW_PCB;  
    // const struct memp_desc memp_RAW_PCB = {
    //     "RAW_PCB",
    //     &memp_stats_RAW_PCB,   //下面语句的作用是是四字节对齐
    //     (((sizeof(struct raw_pcb)) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT - 1U)),  //(24+4-1)& ~(4-1) =24
    //     (MEMP_NUM_RAW_PCB),
    //     memp_memory_RAW_PCB_base,
    //     &memp_tab_RAW_PCB //指向指针的指针
    // };
    /***************将数据代入得到如下结果*****************/
    //这里是根据自己定义的宏得到的     里面的具体值是多少没细看   sizeof(struct raw_pcb)==24               
    // u8_t memp_memory_RAW_PCB_base[((4 * ((24+4-1)& ~(4-1)) ) + 4 - 1U)];  // //雄关漫道真如铁,而今迈步从头越!   [4* ()]
    // static struct stats_mem memp_stats_RAW_PCB;
    // static struct memp* memp_tab_RAW_PCB;
    // const struct memp_desc memp_RAW_PCB = {
    //     "RAW_PCB",            //desc
    //     &memp_stats_RAW_PCB,  //stats
    //     24, //size  (27 & ~3) //经过编译器的预处理,该结构体就保存了每种POOL的内存对齐后的大小
    //     4, //num  内存块的个数
    //     memp_memory_RAW_PCB_base,//base 真正的内存池区域(是一个起始地址,但这个内存块却最后被使用)
    //     &memp_tab_RAW_PCB  //tab   //初始化完成后,它指向最后一个数据块(但这个数据块会被最先使用)
    // };
/*************************************************************************************/
/*************************************************************************************/
//内存池的初始化
    //LwIP初始化的时候, memp_init()会对内存池进行初始化, 正真的内存初始化函数是memp_init_pool()

    // #define LWIP_MEM_ALIGN(addr) ((void*)(((mem_ptr_t)(addr) + MEM_ALIGNMENT - 1) & ~(mem_ptr_t)(MEM_ALIGNMENT - 1)))

    // void memp_init_pool(const struct memp_desc* desc)
    // { //每次只初始化一种POOL
    //     int i;
    //     struct memp* memp;
    //     二级指针
    //     *(desc->tab) = NULL; //其实就是 memp_tab_RAW_PCB   struct memp **tab;
    //     //下面的desc->base其实是    memp_memory_RAW_PCB_base
    //     memp = (struct memp*)LWIP_MEM_ALIGN(desc->base); // ((x+3) & (~3))就是让x的地址尽量大,并且4字节对齐    sanity明智的,头脑健全的
    //     memset(memp, 0, (size_t)desc->num * (MEMP_SIZE + desc->size));  
    //size就是struct raw_pcb的大小  num就是 内存块的个数

    //     /*将内存块单元用链表连起来*/
    //     for (i = 0; i < desc->num; ++i) {  //遍历内存块
    //         memp->next = *desc->tab; //
    //         *desc->tab = memp; //    *desc->tab指向最后一个初始化的memp   第一个初始化的memp指向NULL

    //         /*地址偏移*/
    //         memp = (struct memp*)(void*)((u8_t*)memp + MEMP_SIZE + desc->size);
    //     }
    // }

/********************************************************************************************************************/
//内存分配
    // 内存池初始化之后, 就可通过memp_malloc函数进行申请内存块, 申请内存的时候传入的参数是内存池的类型而非要申请的内存大小, 
    // 系统中所有的内存池类型都会被 记录在memp_pools数组中(内存池描述表), 这样子在申请内存的时候就能很迅速的得到一个对应的内存块.

    // //内存申请函数
    // /*************************************************************************************************************/
    // const struct memp_desc *const memp_pools[MEMP_MAX]= {  //指针指向的内容不可变,指针的指向也不可变
    // 和上面的东东进行对比  里面的元素都是指向struct memp_desc的指针,或者是struct memp_desc类型元素的地址
    // #define LWIP_MEMPOOL(name, num, size, desc) &memp_##name,
    // #include "lwip/priv/memp_std.h"
    // };

    // const struct memp_desc* const memp_pools[MEMP_MAX] = { //上面的东东会产生,如下结果
    //     &memp_RAW_PCB, //这里存储的都是数据块的地址
    //     &memp_UDP_PCB,
    //     &memp_TCP_PCB,
    //     &memp_TCP_PCB_LISTEN,
    //     &memp_TCP_SEG,
    //     &memp_ALTCP_PCB,
    //     ...
    // };
/*****************************************************************************************************************/
    //内存分配函数
    // void* memp_malloc(memp_t type)
    // {
    //     void* memp; //注意这里是void型的指针
    //     LWIP_ERROR("memp_malloc:type < MEMP_MAX", (type < MEMP_MAX), return NULL;);

    //     memp = do_memp_malloc_pool(memp_pools[type]);  //巧妙呀！
           //memp_pools[type]里面装的是一个结构体的地址
    //     return memp;
    // }

    // static void* do_memp_malloc_pool(const struct memp_desc* desc)
    // {  //注意这里是内存分配函数,内存初始化已经完成了
    //     struct memp* memp;

    //     SYS_ARCH_DECL_PROTECT(old_level);
    //     memp = *desc->tab; // *desc->tab指向最后一个初始化的memp   第一个初始化的memp指向NULL

    //     if (memp != NULL) {
    //         *desc->tab = memp->next; //struct memp **tab;是一个指向指针的指针  既然memp已经被分配出去了,那就让tab指向下一个
    //         LWIP_ASSERT("memp_malloc: memp properly aligned", ((mem_ptr_t)memp % MEM_ALIGNMENT) == 0);

    //         SYS_ARCH_UNPROTECT(old_level);
    //         /*cast through u8_t * to get rid of alignment warnings*/
    //         return ((u8_t*)memp + MEMP_SIZE); //这里我们返回的是,直接可以使用的地址
    //     } else { //这里表示desc->tab 为NULL
    //         SYS_ARCH_UNPROTECT(old_level);
    //         LWIP_DEBUGF(MEMP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("memp_malloc: out of memory in pool %s\n", desc - desc));
    //     }
    //     return NULL;
    // }
    //yield:产出,利润,屈服,屈从,放弃  #define MEM_SANITY_REGION_BEFORE 16    MEM_SANITY_REGION_BEFORE_ALIGNED实际值为16                                             
    // #define MEMP_SIZE (LWIP_MEM_ALIGN_SIZE(sizeof(struct memp)) + MEM_SANITY_REGION_BEFORE_ALIGNED)

//内存释放
/***********************************************************************************************/
    // void memp_free(memp_t type, void* mem)
    // {
    //     LWIP_ERROR("memp_free: type < MEMP_MAX", (type < MEMP_MAX), return );

    //     if (mem == NULL) {
    //         return;
    //     }
    //     do_memp_free_pool(memp_pools[type], mem);
    // }

    // /**
    //  *   desc:指向特定的内存池
    //  *   mem: 指向待还到内存池的内存
    //  */
    // static void do_memp_free_pool(const struct memp_desc* desc, void* mem)
    // {
    //     struct memp* memp;
    //     SYS_ARCH_DECL_PROTECT(old_level);

    //     LWIP_ASSERT("memp_free: mem properly aligned", ((mem_ptr_t)mem % MEM_ALIGNMENT) == 0);

    //     /*cast through void * to get rid of alignment warnings*/
    //     memp = (struct memp*)(void*)((u8_t*)mem - MEMP_SIZE); //这里是减   上面是加  说明这里传进来的是直接可用的地址

    //     SYS_ARCH_PROTECT(old_level);

    //     memp->next = *desc->tab; //取的时候是从(*desc->tab)中取的,归还的时候也是还给(*desc->tab)
    //     *desc->tab = memp;

    //     SYS_ARCH_UNPROTECT(old_level);
    // }


    // #if !defined MEM_LIBC_MALLOC || defined __DOXYGEN__
    // #define MEM_LIBC_MALLOC                 0
    // #undef MEM_LIBC_MALLOC
    // #endif  //上面的宏 MEM_LIBC_MALLOC没定义 或者 __DOXYGEN__定义了
    //上下是等效的
    // #if (!defined MEM_LIBC_MALLOC) || defined __DOXYGEN__
    // #define MEM_LIBC_MALLOC                 0
    // #undef MEM_LIBC_MALLOC
    // #endif


/*************************************************************************************************************************/


//动态内存堆(heap)   注意这里讲的是heap  heap  heap  heap  heap  heap  heap  heap  heap  heap  heap  heap  heap  heap  heap
    // 上面讲解的是动态内存池(pool),
    // 现在开始讲解动态内存堆.动态内存堆管理(heap) 可分为两种 : 一种是C标准库自带的内存管理策略, 另一种是LwIP自身实现的内存堆管理策略 
    // 实现两者之间的切换是通过宏值MEM_LIBC_MALLOC 来实现的 二选一 还有, LwIP在自身内存堆和内存池的实现上设计的很灵活.内存池可由内存
    // 堆实现, 反之, 内存堆也可以由内存池实现。通过MEM_USE_POOLS和MEMP_MEM_MALLOC 两个宏定义来实现, 且二者只能 二选一
    // ？？？？？？？？？？MEM_USE_POOLS表示内存堆使用内存池  MEMP_MEM_MALLOC表示内存池使用内存堆 对吗？？？？？？？？？？？？？？？

    // #if MEM_SIZE > 64000L
    // typedef u32_t mem_size_t;
    // #define MEM_SIZE_F U32_F
    // #else
    // typedef u16_t mem_size_t;
    // #define MEM_SIZE_F U16_F
    // #endif /* MEM_SIZE > 64000 */

    // 内存堆的组织结构 struct mem {
    // /**index (-> ram[next]) of the next struct*/
    // mem_size_t next;  //表示目的地址的偏移量
    // /**index (-> ram[prev]) of the previous struct*/
    // mem_size_t prev;  //表示目的地址的偏移量
    // /**1: this area is used; 0: this area is unused*/
    // u8_t used;
    // #if MEMP_OVERFLOW_CHECK
    //     /**this keeps track of the user allocation size for guard checks*/
    //     mem_size_t user_size;  //防护检查
    // #endif
    // };

    //#define MIN_SIZE 12 //所有分配的block的大小都会比MIN_SIZE大

    //   #define SIZEOF_STRUCT_MEM    LWIP_MEM_ALIGN_SIZE(sizeof(struct mem)) 
    //   #define MEM_SIZE_ALIGNED     LWIP_MEM_ALIGN_SIZE(MEM_SIZE)              //这里的LWIP_MEM_ALIGN_SIZE()就是让4byte对齐,然后尽量扩容
    //   #define MEM_SIZE                (15*1024)
    //   #define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) u8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)]
    //   #define LWIP_MEM_ALIGN_BUFFER(size) (((size) + MEM_ALIGNMENT - 1U))
    //   MEM_ALIGNMENT 这里定义的是4

    //LWIP_DECLARE_MEMORY_ALIGNED(ram_heap, MEM_SIZE_ALIGNED + (2U * SIZEOF_STRUCT_MEM));
    //上面就会变为  u8_t ram_heap[ LWIP_MEM_ALIGN_BUFFER(MEM_SIZE_ALIGNED + (2U*SIZEOF_STRUCT_MEM)) ]
    //进一步,变为
    //u8_t ram_heap[ (((MEM_SIZE_ALIGNED + (2U*SIZEOF_STRUCT_MEM)) + MEM_ALIGNMENT - 1U)) ]

    // #define LWIP_RAM_HEAP_POINTER ram_heap

    //   pointer to the heap (ram_heap):
    //   for alignment , ram is now a pointer instead of an array
    // static u8_t* ram;

    /**the last entry , always unused !*/
    // static struct mem* ram_end;

    // #if !NO_SYS
    // static sys_mutex_t mem_mutex;
    // #endif

    // static struct mem* LWIP_MEM_LFREE_VOLATILE lfree; //volatile

// 内存堆初始化 
    //而函数memp_init()函数主要是对 [内存池] 进行初始化。
    //内核初始化的时候,会调用mem_init()函数进行 [内存堆] 的初始化

    // 得区分一下,LWIP_MEM_ALIGN_SIZE 和 LWIP_MEM_ALIGN    
    // #define LWIP_MEM_ALIGN_SIZE(size) (((size) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT - 1U)) //4字节对其,然后空间尽量大
    // mem_ptr_t 指的就是u32_t
    // #define LWIP_MEM_ALIGN(addr) ((void*)(((mem_ptr_t)(addr) + MEM_ALIGNMENT - 1) & ~(mem_ptr_t)(MEM_ALIGNMENT - 1))) //这里和上面差别不大,但这里是为指针准备的
    // struct mem *ram_end;

    // static struct mem* ptr_to_mem(mem_size_t ptr)  //uint8_t * 转成 struct mem* 类型
    // {   //mem_size_t表示 uint16_t 或 uint32_t
    //     return (struct mem*)(void*)&ram[ptr];  //先取地址,再转换成(void *),再转换成(struct mem*)
    // }

    // void mem_init(void)  //得结合图片才能理解
    // {  //初始化后的内存堆就是一个大的空闲内存块
    //     struct mem* mem; //
    //     LWIP_ASSERT("Sanity check alignment", (SIZEOF_STRUCT_MEM & (MEM_ALIGNMENT - 1)) == 0);
    //     /*align the heap*/
    //     ram = (u8_t*)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER); //对指针进行对齐  内存堆对齐后的起始地址被记录在ram中
    //     //u8_t ram_heap[(((MEM_SIZE_ALIGNED + (2U+SIZEOF_STRUCT_MEM))+MEM_ALIGNMENT-1U))] 
    //     /*initialize the start of the heap*/
    //     mem = (struct mem*)(void*)ram;  //每个空闲内存块的前面都需要放置一个mem结构体
    //     mem->next = MEM_SIZE_ALIGNED;//下一个内存块的偏移量为MEM_SIZE_ALIGNED 这相当于直接到内存堆的结束地址了
    //     mem->prev = 0;  //上一个内存块为空
    //     mem->used = 0;                     //中间可用内存数量为MEM_SIZE_ALIGNED - sizeof(struct mem)
    //     /*initialize the end of the heap*/
    //     ram_end = ptr_to_mem(MEM_SIZE_ALIGNED);//指针移动到内存堆末尾的位置,并在那里放置一个mem类型的结构体,并初始化表示内存堆结束的内存块。
    //     ram_end->used = 1; //标记已经使用了内存块,结束的地方没有内存块,不能被分配出去,只能表示已经使用
    //     ram_end->next = MEM_SIZE_ALIGNED; //这里表示指向自身,此处表示已经到了内存堆的结束的地方,没有内存可分配
    //     ram_end->prev = MEM_SIZE_ALIGNED; 
    //     后面这里还有sizeof(struct mem)个字节
    //     MEM_SANITY();

    //     /*initialize the lowest-free pointer to the start of the heap*/
    //     lfree = (struct mem*)(void*)ram;
    //      //MEM_SIZE_ALIGNED就是(15*1024)进行4字节对齐之后的大小
    //     MEM_STATS_AVAIL(avail, MEM_SIZE_ALIGNED); //统计用的

    //     if (sys_mutex_new(&mem_mutex) != ERR_OK) {
    //         LWIP_ASSERT("failed to create mem_mutex", 0);
    //     }
    // }

    //在系统运行的时候,随着内存的分配与释放,lfree指针的指向地址不断改变,都指向内存堆中低地址空闲内存块,而ram_end则不会改变,
    //它指向系统中最后一个内存块,也就是内存堆的结束地址

//内存(堆)分配
    //分配的内存空间会受到内存对其的影响,可能会比申请的内存略大(比如用户要申请22个字节的内存, 而CPU是按照4字节内存对齐的, 
    //那么分配的时候就会申请24个字节的内存块)
    //内存在申请成功后返回的是内存块的起始地址,但是还未进行内存初始化, 可能包含任意的随机数据, 内存堆是一个全局变量, 
    //在操作系统的环境中进行申请内存块是不安全的,所以LwIP使用互斥量实现了对临界资源的保护, 在多个线程同时申请或者释放的时候, 
    //会因为互斥量的保护而产生延迟.   mem_size_t表示uint16_t  或者 uint32_t  

    // static mem_size_t mem_to_ptr(void* mem)
    // { //计算出,mem指向的地址到,ram指向的地址,之间的字节大小 ram指向内存堆的起始地址
    //     return (mem_size_t)((u8_t*)mem - ram);
    // }

    // void* mem_malloc(mem_size_t size_in) //实际分配给外部的内存是按4byte对齐的
    // {                                    //32位的计算机中字长是4Byte  
    //                                      //半字(uint16_t)的地址只能是2的整数倍
    //                                      //字(uint32_t)
    //     mem_size_t ptr, ptr2, size;  //uint16_t 或者uint32_t
    //     struct mem *mem, *mem2;
    //     LWIP_MEM_ALLOC_DECL_PROTECT();  //RT-thread中使用的是semaphore

    //     if (size_in == 0) {
    //         return NULL;
    //     }
    //     size = (mem_size_t)LWIP_MEM_ALIGN_SIZE(size_in); //将其大小按4字节对齐,并且空间尽量大
    //     if (size < MIN_SIZE_ALIGNED) {
    //         size = MIN_SIZE_ALIGNED;
    //     }

    //     if ((size > MEM_SIZE_ALIGNED) || (size < size_in)) //按4字节对齐后反而变小了
    //     { //超过了总大小               ||   可以给的比需求的还少
    //         return NULL;             有缘相见,互道年轻
    //     }
    //     sys_mutex_lock(&mem_mutex);
    //     LWIP_MEM_ALLOC_PROTECT();

    //     /*遍历空闲内存块链表*/  //lfree指向低地址的空闲内存块
    //     for (ptr = mem_to_ptr(lfree); ptr < MEM_SIZE_ALIGNED - size; ptr = ptr_to_mem(ptr)->next) //烟霞
    //     { // ptr指向当前可用的地址空间的首地址(这里只是一个偏移地址相对于堆内存块的首地址而言的)  
    //       // ptr < MEM_SIZE_ALIGNED - size 成立表明可以分配内存  
    //         mem=ptr_to_mem(ptr);//将这个相对地址,转变成struct mem *类型的指针(传入的参数是相对地址,相对于内存堆首地址而言的)
    //                            //mem->next相当于ram_end指向本块的最后ram_end 
    //         if ((!mem->used) && (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size) //这里表示有东东可用(到底是刚好,还是有剩余,未知)
    //         { // 没有被使用并且实际可用的空间大小得大于等于size  //mem->next - (ptr + SIZEOF_STRUCT_MEM)表示内存堆中实际还剩多少用户数据(注意是实际可用的)
    //             if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >= (size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED)) //这里确定有剩余
    //             { //SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED  这是又一个单元
    //                 ptr2 = (mem_size_t)(ptr + SIZEOF_STRUCT_MEM + size); //这里应该是指向下一个东东
    //                 LWIP_ASSERT("invalid next ptr", ptr2 != MEM_SIZE_ALIGNED); //当为真时,就不会打印了
    //                 /*create mem2 struct*/
    //                 mem2 = ptr_to_mem(ptr2); //对第二个指针进行初始化
    //                 mem2->used = 0;
    //                 mem2->next = mem->next; //接替任务
    //                 mem2->prev = ptr; //指向第一个
    //                 /*and insert it between mem and mem->next*/
    //                 mem->next = ptr2;
    //                 mem->used = 1; //

    //                 if (mem2->next != MEM_SIZE_ALIGNED) { //意思就是说:  mem2->next还没到底!  但指向了某个单元(表示下面还有人)
    //                     ptr_to_mem(mem2->next)->prev = ptr2; //不管下面是否有人,上面都指向了下面,如果下面有人,那就让下面指向上面
    //                 }
    //                 MEM_STATS_INC_USED(used, (size + SIZEOF_STRUCT_MEM)); //这里是debug用的
    //             } else {
    //                 mem->used = 1; //这里也标记mem->used是使用过的  应该是指没有下一个了
    //                 MEM_STATS_INC_USED(used, mem->next - mem_to_ptr(mem)); //这里是debug用的
    //             }

    //             if (mem == lfree) {
    //                 struct mem* cur = lfree; //

    //                 /*find next free block after mem and update lowest free pointer*/
    //                 while (cur->used && cur != ram_end) { //当前东东是使用过的     并且没有到最后
    //                     cur = ptr_to_mem(cur->next);
    //                 }
    //                 //这里的东东就是没有使用过的,如果还是使用过的,那么就会报错
    //                 lfree = cur; //指向没有使用过的东东  两个都为假时  lfree != ram_end  lfree->used等于1
    //                 LWIP_ASSERT("mem_malloc: !lfree->used", ((lfree == ram_end) || (!lfree->used))); //没有到达最后,并且是使用过的
    //             } //内存堆的使用是由低地址向高地址来的
    //             LWIP_MEM_ALLOC_UNPROTECT();
    //             sys_mutex_unlock(&mem_mutex);
    //             LWIP_ASSERT("mem_malloc: allocated memory not above ram_end.",
    //                 (mem_ptr_t)mem + SIZEOF_STRUCT_MEM + size <= (mem_ptr_t)ram_end); //有点不解？？？？？？？
    //             LWIP_ASSERT("mem_malloc: allocated memory properly aligned."((mem_ptr_t)mem + SIZEOF_STRUCT_MEM) % MEM_ALIGNMENT == 0); //对齐了就不会打印了呀?????
    //             LWIP_ASSERT("mem_malloc: sanity check alignment",
    //                 (((mem_ptr_t)mem) & (MEM_ALIGNMENT - 1)) == 0); //4字节对齐了,就会是0

    //             MEM_SANITY();
    //             return (u8_t*)mem + SIZEOF_STRUCT_MEM + MEM_SANITY_OFFSET; //返回的地址是直接可以使用的
    //         }
    //     }
    //     MEM_STATS_INC(err);
    //     LWIP_MEM_ALLOC_UNPROTECT();
    //     sys_mutex_unlock(&mem_mutex);
    //     LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SERIOUS,
    //         ("mem_malloc: could not allocate %" S16_F " bytes\n", (s16_t)size));

    //     return NULL;
    // }  //这里分配的内存地址,前面有一个结构体,结构体里面存有该内存的链接信息

内存堆释放
static int mem_link_valid(struct mem* mem)
{
    struct mem *nmem, *pmem;
    mem_size_t rmem_idx;
    rmem_idx = mem_to_ptr(mem); //算出距离内存首部的字节数  其实就是绝对地址变成相对地址
    nmem = ptr_to_mem(mem->next); //将其转化成mem类型的指针 相对地址变成绝对地址
    pmem = ptr_to_mem(mem->prev);  //相对地址变成绝对地址

    /**
         *  mem->prev != rmem_id(prev没有指向自己)  表明mem不是最后一个块同时也不是第一个(内存首部的块),就是表示是中间的块     pmem->next != rmem_idx 这有一点说不过去  中间的块一定有pmem->next==rmem_idx
         * 
         *  nmem != ram_end 表明nmem不是最后一个块(即mem不是最后一个块,也不是倒数第二个块)     那么一定有nmem->prev == rmem_idx      nmem->prev != rmem_idx  前面条件成立时,这个不成立
         * 
         *  综上，第一个成立时,后面一个就不会成立了!   
         *  
         *  mem->next > MEM_SIZE_ALIGNED 和   mem->prev > MEM_SIZE_ALIGNED 很容易理解,相对地址偏移不能超过 MEM_SIZE_ALIGNED
         */

    if ((mem->next > MEM_SIZE_ALIGNED) || (mem->prev > MEM_SIZE_ALIGNED) || ((mem->prev != rmem_idx) && (pmem->next != rmem_idx)) || ((nmem != ram_end) && (nmem->prev != rmem_idx))) { //这个表达式为毛这样?????????????
        return 0; //表明出现错误！！！！！！ 这里表明链接不正常!!
    }
    return 1;
}

static void plug_holes(struct mem* mem)
{ //这里只是将前后的内存合并了
    struct mem* nmem;
    struct mem* pmem;

    LWIP_ASSERT("plug_holes: mem >= ram", (u8_t*)mem >= ram); //正常!!!!!!!!!  为真时,才不会打印
    LWIP_ASSERT("plug_holes: mem < ram_end", (u8_t*)mem < (u8_t*)ram_end);
    LWIP_ASSERT("plug_holes: mem->used==0", mem->used == 0);
    /*plug hole forward*/
    LWIP_ASSERT("plug_holes: mem->next <= MEM_SIZE_ALIGNED", mem->next <= MEM_SIZE_ALIGNED);  //就是相对位置不能超过 MEM_SIZE_ALIGNED
    nmem = ptr_to_mem(mem->next); //找到下一个

    //mem == nmem就表明 mem是最后一个    当mem是最后一个时,有nmem->used==1
    if (mem != nmem && nmem->used == 0 && (u8_t*)nmem != (u8_t*)ram_end) { //当nmem等于ram_end 时  就会有  mem等于nmem等于ram_end
        /*if mem->next is unused and not end of ram, combine mem and mem->next*/
        if (lfree == nmem) { //将lfree指针下移
            lfree = mem; //这里的mem是要释放的东东
        }
        mem->next = nmem->next; //边界更替
        if (nmem->next != MEM_SIZE_ALIGNED) //  ram_end = ptr_to_mem(MEM_SIZE_ALIGNED);
        { //nmem的下一个不是最后一个
            ptr_to_mem(nmem->next)->prev = mem_to_ptr(mem); //
        }
    }
    /*plug hole backward*/
    pmem = ptr_to_mem(mem->prev); //ptr_to_mem(pmem->next)==mem
    //pmem != mem 表示mem不是最后一个,也不是第一个
    if (pmem != mem && pmem->used == 0) { //当mem刚刚初始化的时候  有mem->prev==0  ptr_to_mem(mem->prev)==ram==mem 差不多是这个理儿
        /*if mem->prev is unused, combine mem and mem->prev*/
        if (lfree == mem) {
            lfree = pmem;
        }
        pmem->next = mem->next;
        if (mem->next != MEM_SIZE_ALIGNED) { // ram_end->next和 ram_end->prev  都指向了 ram_end
            ptr_to_mem(mem->next)->prev = mem_to_ptr(pmem);
        }
    }
}

void mem_free(void* rmem) //rmem指向待返回到内存的数据的可用部分(实际还要减去SIZEOF_STRUCT_MEM)
{
    struct mem* mem;
    LWIP_MEM_FREE_DECL_PROTECT();

    if (rmem == NULL) {
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
            ("mem_free(p==NULL) was called.\n"));
        return;
    }
    if ((((mem_ptr_t)rmem) & (MEM_ALIGNMENT - 1)) != 0) { //要释放的内存地址,没有按照4byte对齐
        LWIP_MEM_ILLEGAL_FREE("mem free: sanity check alignment");
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("mem_free: sanity check alignment\n")); //warning   serious    severe
        /*protect mem stats from concurrent access*/
        MEM_STATS_INC_LOCKED(illegal);
        return;
    }
    //
    mem = (struct mem*)(void*)((u8_t*)rmem - (SIZEOF_STRUCT_MEM + MEM_SANITY_OFFSET)); //这里是实际指向的

    if ((u8_t*)mem < ram || (u8_t*)rmem + MIN_SIZE_ALIGNED > (u8_t*)ram_end) { //ram 内存起始地址    这里表示总之就是越界了
        LWIP_MEM_ILLEGAL_FREE("mem_free: illegal memory");
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("mem_free: illegal memory\n"));
        /*protect mem stats from concurrent access*/
        MEM_STATS_INC_LOCKED(illegal);
        return;
    }

    /*protect the heap from concurrent access*/
    LWIP_MEM_FREE_PROTECT(); //这里是上锁  有两种 第一种是彻底把中断给关掉,第二种是利用RTOS的IPC机制如MUTEX来实现

    /*mem has to be in a used state*/
    if (!mem->used) { //这个是没有使用的
        LWIP_MEM_ILLEGAL_FREE("mem_free: illegal\memory: double free");
        LWIP_MEM_FREE_UNPROTECT();
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("mem_free: illegal memory: double free?\n"));
        /*protect mem stats from concurrent access*/
        MEM_STATS_INC_LOCKED(illegal);
        return;
    }

    if (!mem_link_valid(mem)) //判断一下内存块在链表中的连接是否正常
    { //当返回的值是0时,就会进来
        LWIP_MEM_ILLEGAL_FREE("mem_free: illegal memory: non-linked: double free");
        LWIP_MEM_FREE_UNPROTECT();
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("mem_free: illegal memory: non-linked: double free?\n"));
        MEM_STATS_INC_LOCKED(illegal);
        return;
    }
    /*mem is now unused*/
    mem->used = 0;
    上面如果是0, 那么就会导致返回, 也就不会执行到这里来

        if (mem < lfree)
    {
        /*the newly freed struct is now the lowest*/
        lfree = mem; //使用时,都是从低处开始!!!!!!!!!!!
    }
    MEM_STATS_DEC_USED(used, mem->next - (mem_size_t)((u8_t*)mem - ram));

    /*finally, see if prev or next are free also*/
    plug_holes(mem);

    MEM_SANITY();
    LWIP_MEM_FREE_UNPROTECT(); //开锁
}

内存碎片

        内存泄漏: 就是用户在调用内存分配函数后,没有及时或者进行错误的内存释放操作,一次两次这样子没有什么影响,但当内存堆中的内存用完后,最终导致内存耗尽
    无法申请内存,出现死机等现象!!!

        不建议使用C标准库这种方式管理内存！！！


    MEM_LIBC_MALLOC     为0表示不使用C标准库自带的内存分配策略,使用LwIP提供的内存堆分配策略!    为1表示使用C标准库自带的分配策略

    LwIP的动态内存管理策略又分为两种实现形式:
            1.通过内存堆(HEAP)管理策略来实现内存管理(大数组)
            2.通过内存池(POOL)管理策略来实现内存管理(事先开辟好的内存池)

        宏定义及其内存分配策略!
    MEMP_MEM_MALLOC             MEM_USE_POOLS           内存分配策略
         0                           0                 LwIP中默认的宏定义，内存池与
                                                       内存堆独立实现,互不相干

         0                           1                 内存堆的实现由内存池实现

         1                           0                 内存池的实现由内存堆实现

         1                           1                  不允许的方式

     
    当要使用内存池的方式实现内存堆分配,则需要将MEM_USE_POOLS与MEMP_USE_CUSTOM_POOLS定义为1,并且宏定义MEMP_MEM_MALLOC必须为0

    动态内存池:  申请大小必须是指定固定大小字节的值（如4、8、16等等)

    /******************************************************************************************************************/

    /******************************************************************************************************************/ 
    TCP/IP是一种数据通讯机制,故协议栈的实现本质上就是对数据包进行处理
    提高协议栈工作效率的关键就是: 协议栈各层能对数据包进行灵活的处理,同时减小数据在各层间传递时的时间与空间开销

    BSD实现中,一个描述数据包的结构体叫做mbuf,在LwIP中也有个类似的结构,称之为pbuf

    LwIP各层在数据处理的时候极力避免进行数据拷贝 

        TCP/IP的分层思想:  标准的TCP/IP协议栈中,各层之间都是一个独立的模块(层次分明,分工明确),,但LwIP只是一个轻量级TCP/IP协议栈,故LwIP并没有
    采用很明确的分层结构(就是各层之间的部分数据和结构体和实现原理在其他层是可见的),简单来说就是传输层(TCP/UDP)知道IP层(IPv4 IPv6)是如何封装数
    据的,传输数据的，IP层知道链路层是怎么封装数据的等等
        模糊分层的目的就是提高效率,例如链路层(MAC层)完成数据包在物理线路上传输的封装;
        IP层完成数据包的选择和路由, 负责将数据包发送到目标主机;
    传输层(TCP/UDP)负责根据IP地址将数据包传输到指定主机,端口号识别一台主机的线程,向不同的应用层递交数据

        严格按照TCP协议的分层思想会导致数据包在各层递交产生内存拷贝问题，影响性能。为了节省时间和空间的开销，LwIP没有遵循严格的分层机制，各层次之间
    存在交叉存取的现象，提高效率。由于LwIP的内存共享机制，使得应用程序能直接对协议栈内核的内存区域直接操作，减少时间和空间的损耗。

        LwIP的线程模型:
    1.将协议栈的实现划分在多个线程之中,如让协议栈的各个层次都独立成为一个线程。这样子各个层次都有严格分层结构(编程变得简便了,但使得协议栈的效率变得很低下!)
    2.协议栈和操作系统融合,成为操作系统的一部分,这样子用户线程和协议栈内核之间都是通过操作系统提供的函数来实现的,协议栈各层之间与用户线程就没有很严格的分层结构,
      各层之间能交叉存取,提高效率
    3.但是LwIP没有采用上面两种方式,而是采取了另一种方式,让协议栈内核与操作系统相互独立,协议栈仅仅作为操作系统的一个独立线程存在,
        1)用户程序能驻留在协议栈内部,协议栈通过回调函数实现用户与协议栈之间的数据交互.                  其实就是RAW API编程  (实时性好)
        2)用户程序作为一个单独线程,与协议栈使用系统的信号量和邮箱等IPC通讯机制联系起来,进行数据交互.     其实就是NETCONN API和Socket API (实时性没那么好)
    

    pbuf结构体,pbuf就是一个描述协议栈中数据包的数据结构,pbuf数据结构的定义具体如下
    /*Main packet buffer struct*/       //做一个高价值的人  packet:信息包,数据包
    struct pbuf
    {//信息包缓冲器结构体
        /*next pbuf in single linked pbuf chain*/
        struct pbuf* next; //是一个pbuf类型的指针,指向下一个pbuf,因为网络中的数据包可能很大,而一个pbuf可以管理的数据包大小有限,此时就会采用
            //链表的形式将所有的pbuf包连接起来,从而完整的描述一个数据包,被连接起来的pbuf包被称为pbuf链表

        /*pointer to the actual data in the buffer*/
        void* payload; //负载  //是一个指向数据区域的指针,指向该pbuf管理的数据区域起始地址,这里的数据区域可以是紧跟在pbuf结构体地址后面的RAM空间
            //也可以是ROM中的某个地址上,取决于pbuf的类型

        u16_t tot_len; //记录着当前pbuf及其后续pbuf所有数据的长度,例如如果当前pbuf是pbuf链表上第一个数据结构,那么tot_len就记录着整个pbuf链表
            //中所有pbuf中数据的长度;如果当前pbuf是链表上最后一个数据结构,那就记录着当前pbuf的长度
            //每个pbuf的tot_len字段记录的就是自身及其后面的pbuf总大小
        /*length of this buffer*/
        u16_t len; //表示当前pbuf中有效的数据长度

        u8_t type_internal; //表示pbuf的类型,LwIP中有4种pbuf的类型,并且使用了一个枚举类型的数据结构定义他们,
            //因为不同类型的pbuf可以组成pbuf链表
        /*miscellaneous flag*/
        u8_t flags;

        LWIP_PBUF_REF_T ref; //ref表示该pbuf被引用的次数(这里的引用表示有其它指针指向当前pbuf,这里的指针可以是pbuf的next指针,也可以是其它任意形式
            //的指针,初始化一个pbuf的时候，ref会被设置为1,因为该pbuf的地址会被返回给一个指针变量,当有其它指针指向pbuf的时候,就必须
            //调用相关函数将ref字段加1)

        /*For incoming packets , this contains the input netif's index*/
        u8_t if_idx; //用于记录传入的数据包中输入netif的索引,也就是netif中的num(用来标示使用同种驱动类型的不同网卡)字段
    }

#define PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS 0x80
#define PBUF_TYPE_FLAG_DATA_VOLATILE 0x40
#define PBUF_TYPE_ALLOC_SRC_MASK 0x0F
#define PBUF_ALLOC_FLAG_RX 0x0100
#define PBUF_ALLOC_FLAG_DATA_CONTIGUOUS 0x0200

#define PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP 0x00
#define PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF 0x01
#define PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL 0x02
#define PBUF_TYPE_ALLOC_SRC_MASK_APP_MIN 0x03
#define PBUF_TYPE_ALLOC_SRC_MASK_APP_MAX PBUF_TYPE_ALLOC_SRC_MASK

    pbuf的类型有四种,
        分别是PBUF_RAM, PBUF_POOL, PBUF_ROM, PBUF_REF typedef enum { //contiguous 相邻的
            PBUF_RAM = (PBUF_ALLOC_FLAG_DATA_CONTIGUOUS | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP),

            PBUF_ROM = PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF,

            PBUF_REF = (PBUF_TYPE_FLAG_DATA_VOLATILE | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF),

            PBUF_POOL = (PBUF_ALLOC_FLAG_RX | PBUF_TYPE_FLAG_STRUCT_DATA_CONTIGUOUS | PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL)
        } pbuf_type;

    /********************************************************************************************************************************/
    PBUF_RAM类型的pbuf空间是通过内存堆分配而来的, 一般协议栈中要发送的数据都是采用这种形式, 且pbuf内存块包含数据空间以及pbuf数据结构区域
                                                                                                //rt-thread中的malloc函数就是采用内存堆分配函数的
                                                                                                申请PBUF_RAM类型的pbuf
        /*函数原型*/
        struct pbuf*
        pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type);

    /*使用例子*/
    struct pbuf* p;
    p = pbuf_alloc(PBUF_RAM, (u16_t)(req_len + 1), PBUF_RAM);
    p = pbuf_alloc(PBUF_TRANSPORT, 1472, PBUF_RAM);

    /*PBUF_RAM结构体具体的排布方式:  最前面就是pbuf结构体,中间是layer(offset)就是各层协议的首部(如TCP报文首部,IP首部,以太网帧首部),最后就是数据区域*/
    /*******************************************************************************************************************************/

    /*******************************************************************************************************************************/
    PBUF_POOL类型的pbuf与PBUF_RAM类型的pbuf都是差不多的, 其pbuf结构体与数据缓冲区也是存在于连续的内存块中, 但它的空间是通过内存池分配的, (使用内存池 分配策略的优势就是
                                                                                                                                             : 可以在极短的时间分配得到, 因为其大小是已知的, 且固定的),
        网卡接受数据的时候LwIP一般就使用这种类型的pbuf来存储接收到的数据。申请PBUF_POOL类型的时候,
        协议栈会在内存池中分配适当的内存池个数以满足需要的数据区域大小.

        除此之外,
        在系统进行内存初始化的时候, 还会初始化两个与pbuf相关的内存池, 分别为MEMP_PBUF, MEMP_PBUF_POOL

        /**此处的东西存在于memp_std.h文件中**/
        LWIP_MEMPOOL(PBUF, MEMP_NUM_PBUF, sizeof(struct pbuf), "PBUF_REF/ROM") //对应MEMP_PBUF内存池
        LWIP_PBUF_MEMPOOL(PBUF_POOL, PBUF_POOL_SIZE, PBUF_POOL_BUFSIZE, "PBUF_POOL")

            MEMP_PBUF内存池是专门用于存放pbuf数据结构的内存池,
        主要用于PBUF_ROM, PBUF_REF类型的pbuf, 其大小为sizeof(struct pbuf), 内存块的数量为MEMP_NUM_PBUF

        MEMP_PBUF_POOL则包含pbuf结构与数据区域,
        也就是PBUF_POOL类型的pbuf, 内存块的大小为PBUF_POOL_BUFSIZE, 其值由用户自己定义, 默认为590(536 + 40 + 0 + 14) 字节,
        内存块的个数为PBUF_POOL_SIZE
        /*******************************************************************************************************************************/

        /*******************************************************************************************************************************/
        PBUF_ROM和PBUF_REF类型的pbuf基本是一样的,
        他们在内存池申请的pbuf不包含数据区域, 只有pbuf结构体, 即MEMP_PBUF类型的POOL,
        PBUF_ROM类型的pbuf的数据区域存储在ROM中, 是一段静态数据, PBUF_REF类型的pbuf的数据区域存储在RAM空间中
        /*******************************************************************************************************************************/

        /*******************************************************************************************************************************/
        typedef enum { //各层协议首部大小!       0bytes                    14bytes           20bytes          20bytes
            PBUF_TRANSPORT = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN, //传输层  (UDP,TCP)

            PBUF_IP = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN, //网络层  (IP)

            PBUF_LINK = PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN, //链路层  (以太网)

            PBUF_RAW_TX = PBUF_LINK_ENCAPSULATION_HLEN, //原始层

            PBUF_RAW = 0 //原始层
        } pbuf_layer;

    /*******************************************************************************************************************************/
    static void pbuf_init_alloced_pbuf(struct pbuf* p, void* payload, u16_t tot_len, u16_t len, pbuf_type type, u8_t flags)
    {
        p->next = NULL;
        p->payload = payload;
        p->tot_len = tot_len;
        p->len = len;
        p->type_internal = (u8_t)type; //四种类型的
        p->flags = flags;
        p->ref = 1;
        p->if_idx = NETIF_NO_INDEX;
    }

    // LWIP_MEMPOOL(PBUF,           MEMP_NUM_PBUF,            sizeof(struct pbuf),           "PBUF_REF/ROM")
    // LWIP_PBUF_MEMPOOL(PBUF_POOL, PBUF_POOL_SIZE,           PBUF_POOL_BUFSIZE,             "PBUF_POOL")

    struct pbuf* pbuf_alloc_reference(void* payload, u16_t length, pbuf_type type)
    {
        struct pbuf* p;
        LWIP_ASSERT("invalid pbuf_type", (type == PBUF_REF) || (type == PBUF_ROM)); //注意这里的东东
        /*only allocate memory for the pbuf structure*/
        p = (struct pbuf*)memp_malloc(MEMP_PBUF); //MEMP的类型  (内存池的类型)   特指PBUF_REF/ROM(不包含layer和数据区域)
        if (p == NULL) {
            return NULL;
        }
        pbuf_init_alloced_pbuf(p, payload, length, length, type, 0);
        return p;
    }

    struct pbuf* pbuf_alloc(pbuf_layer layer, u16_t length, pbuf_type type)
    { //                                       length表示数据区域的大小
        struct pbuf* p;
        u16_t offset = (u16_t)layer; //这里是一个枚举类型  表示各协议层首部的大小

        switch (type) {
        case PBUF_REF: /*fall through*/
        case PBUF_ROM:
            p = pbuf_alloc_reference(NULL, length, type); //这里表示不形成链表
            break; //没有条件要创造条件,现在有条件了那么就得大干一场
        case PBUF_POOL: //内存池分配时,每个内存块的大小是相同的,直接使用即可
        { //这里的payload不为NULL
            struct pbuf *q, *last;
            u16_t rem_len; /*remaining length*/
            p = NULL;
            last = NULL;
            rem_len = length; //pbuf的payload的长度 数据区域的长度(一个pbuf装不下,那么得用多个pbuf才行)
            do {
                u16_t qlen; //这里分配的大小应该是  SIZEOF_STRUCT_PBUF +  PBUF_POOL_BUFSIZE_ALIGNED 的大小
                q = (struct pbuf*)memp_malloc(MEMP_PBUF_POOL); //在这个类型挺难搞的
                if (q == NULL) //上面是内存池分配函数
                {
                    PBUF_POOL_IS_EMPTY(); //这里是干啥的？？？
                    /*free chain so far allocated*/
                    if (p) //上面是q,这里是p
                    {
                        pbuf_free(p);
                    }
                    /*bail out unsuccessfully*/
                    return NULL; //默认为536   +40+ 0 + 14
                } //LWIP_MEM_ALIGN_SIZE(TCP_MSS+40+PBUF_LINK_ENCAPSULATION_HLEN+PBUF_LINK_HLEN)  == PBUF_POOL_BUFSIZE_ALIGNED
                qlen = LWIP_MIN(rem_len, (u16_t)(PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset))); //表示当前pbuf中有效的数据长度
                //LWIP_MIN(x,y)  取x,y中小的那一个    PBUF_POOL_BUFSIZE_ALIGNED只表示数据的长度吗???
                //这里的 rem_len即是 总的长度 包括当下,及后面的
                pbuf_init_alloced_pbuf(q, LWIP_MEM_ALIGN((void*)((u8_t*)q + SIZEOF_STRUCT_PBUF + offset)), rem_len, qlen, type, 0);
                LWIP_ASSERT("pbuf_alloc: pbuf q->payload properly aligned", ((mem_ptr_t)q->payload % MEM_ALIGNMENT) == 0);
                LWIP_ASSERT("PBUF_POOL_BUFSIZE must be bigger than MEM_ALIGNMENT", (PBUF_POOL_BUFSIZE_ALIGNED - LWIP_MEM_ALIGN_SIZE(offset)) > 0);
                if (p == NULL) { //第一次的时候,这个p肯定是为NULL
                    /*allocated head of pbuf chain (into p)*/
                    p = q; //那么现在p就指向了q    其中q是指向新申请的内存的  这里是让p指向pbuf链表的首部
                } else {
                    /*make previous pbuf point to this pbuf*/
                    last->next = q;
                }
                last = q; //p previous指向头节点  last指向尾节点
                rem_len = (u16_t)(rem_len - qlen); //PBUF_POOL_BUFSIZE_ALIGNED这个应该就是指数据区域(layer + 数据区域)
                offset = 0;
            } while (rem_len > 0); //直到把rem_len分配光
            break;
        }
        case PBUF_RAM: {
            u16_t payload_len = (u16_t)(LWIP_MEM_ALIGN_SIZE(offset) + LWIP_MEM_ALIGN_SIZE(length));
            mem_size_t alloc_len = (mem_size_t)(LWIP_MEM_ALIGN_SIZE(SIZEOF_STRUCT_PBUF) + payload_len); //PBUF结构体 + offset + length

            if ((payload_len < LWIP_MEM_ALIGN_SIZE(length)) || (alloc_len < LWIP_MEM_ALIGN_SIZE(length))) {
                return NULL;
            }

            /*If pbuf is to be allocated in RAM ,allocate memory for it*/ //有付出就有回报  ！！！
            p = (struct pbuf*)mem_malloc(alloc_len); //这个是从内存堆中来分配的
            if (p == NULL) { //这里表示没有空间可以使用了
                return NULL;
            }
            //                    length length
            pbuf_init_alloced_pbuf(p, LWIP_MEM_ALIGN((void*)((u8_t*)p + SIZEOF_STRUCT_PBUF + offset)), length, length, type, 0);
            //                                                               这里说明了 PBUF_RAM类型的东东不是一个链表,而PBUF_POOL类型的却是
            LWIP_ASSERT("pbuf_alloc: pbuf->payload properly aligned", ((mem_ptr_t)p->payload % MEM_ALIGNMENT) == 0);
            break;
        }
        default:
            LWIP_ASSERT("pbuf_alloc : erroneous type", 0);
            return NULL;
        }
        LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_alloc(length=%" U16_F ") == %p\n", length, (void*)p));
        return p;
    }

#define LWIP_ASSERT(message, assertion)    \
    do {                                   \
        if (!(assertioon)) {               \
            LWIP_PLATFORM_ASSERT(message); \
        }                                  \
    } while (0)

    //上面是LWIP_ASSERT的定义

    LwIP能快速处理的优势 : 当数据报往下递交的时候, 其他层直接填充对应的协议首部即可, 无需对数据进行拷贝等操作。

#define LWIP_DEBUGF(debug, message)                                                                                                 \
    do {                                                                                                                            \
        if (((debug)&LWIP_DBG_ON) && ((debug)&LWIP_DBG_TYPES_ON) && ((s16_t)((debug)&LWIP_DBG_MASK_LEVEL) >= LWIP_DBG_MIN_LEVEL)) { \
            LWI_PLATFORM_DIAG(message);                                                                                             \
            if ((debug)&LWIP_DBG_HALT) {                                                                                            \
                while (1)                                                                                                           \
                    ;                                                                                                               \
            }                                                                                                                       \
        }                                                                                                                           \
    } while (0);

    typedef void (*pbuf_free_custom_fn)(struct pbuf* p); 
    
    struct pbuf_custom { //The actual pbuf
        struct pbuf pbuf;
        pbuf_free_custom_fn custom_free_function; //这里是一个函数指针
    };

    u8_t pbuf_free(struct pbuf* p)
    { //
        u8_t alloc_src;
        struct pbuf* q;
        u8_t count;

        if (p == NULL) {
            LWIP_ASSERT("p != NULL", p != NULL); //当p==NULL时就会有这样的结果
            LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("pbuf_free(p==NULL) was called.\n"));
            /**
             *  ALL  WARNING  SERIOUS SEVERE 
             */
            return 0;
        }
        LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free(%p)\n", (void*)p));

        PERF_START;

        count = 0;
        /**
         * de-allocate all consecutive pbufs from the head of the chain that obtain a zero reference count after decrementing
         */
        while (p != NULL) {
            LWIP_PBUF_REF_T ref; //uint8_t ref;
            SYS_ARCH_DECL_PROTECT(old_level); //int16_t old_level;
            /**
             *   Since decrementing ref cannot be guaranteed to be a single machine operation 
             *   we must protect it . We put the new ref into a local variable to prevent further protection
             */
            SYS_ARCH_PROTECT(old_level); //    old_level = sys_arch_protect();
                //    sys_prot_t sys_arch_protect(void)
                //    {
                //        vPortEnterCritical();   //关总中断
                //        return 1;
                //    }
            /*all pbufs in a chain are referenced at least once*/ //ref初始化时就是1
            LWIP_ASSERT("pbuf_free: p->ref > 0", p->ref > 0); //当等于0,时就会有输出
            /*decrease reference count (number of pointers to pbuf)*/
            ref = --(p->ref);
            SYS_ARCH_UNPROTECT(old_level); //   sys_arch_unprotect(old_level)
                //   void sys_arch_unprotect(sys_prot_t pval)
                //   {
                //       (void )pval;
                //       vPortExitCritical();
                //   }
            /*this pbuf is no longer referenced to ?*/
            if (ref == 0) { //remember next pbuf in chain for next iteration
                q = p->next; //这里的p是要被释放的????  这里的q是备份用的
                LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: deallocating %p\n", (void*)p));
                alloc_src = pbuf_get_allocsrc(p); // #define pbuf_get_allocsrc(p)   ((p)->type_internal & PBUF_TYPE_ALLOC_SRC_MASK)
                    //                               PBUF_TYPE_ALLOC_SRC_MASK==0x0F
                //上面是获取p所指向的pbuf的类型
#if LWIP_SUPPORT_CUSTOM_PBUF
                /*is this a custom pbuf ?*/
                if ((p->flags & PBUF_FLAG_IS_CUSTOM) != 0) { //(p->flags & 0x02) != 0
                    struct pbuf_custom* pc = (struct pbuf_custom*)p; //p是要被释放的(前一个)  q表示下一个(头被释放了)
                    LWIP_ASSERT("pc->custom_free_function != NULL", pc->custom_free_function != NULL); //等于NULL时会报错
                    pc->custom_free_function(p);
                } else
#endif
                { //上面是针对自定义的pbuf的，这里就是标准的pbuf了
                    /*is this a pbuf from the pool ?*/
                    if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL) {
                        memp_free(MEMP_PBUF_POOL, p);
                    } /*is this a ROM or RAM referencing pbuf?*/
                    else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF) {
                        memp_free(MEMP_PBUF, p);
                    } /*type==PBUF_RAM*/
                    else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP) {
                        mem_free(p);
                    } else { /*@todo: support freeing other types*/
                        LWIP_ASSERT("invalid pbuf type", 0);
                    }
                }
                count++;
                p = q; //现在p后移动  现在还原
                /*p->ref >0 ,this pbuf is still referenced to */
                /*(and so the remaining pbufs in chain as well)*/
            } else {
                LWIP_DEBUGF(PBUF_DEBUG | LWIP_DBG_TRACE, ("pbuf_free: %p has ref %" U16_F ", ending here .\n", (void*)p, (u16_t)ref));
                /*stop walking through the chain*/
                p = NULL;
            }
        }
        PERF_STOP("pbuf_free");
        /*return number of de-allocated pbufs*/
        return count;
    }

    能被内核释放掉的pbuf数据包只能是首节点或者其他地方未被引用过的节点, 如果用户错误的调用了pbuf释放函数, 将pbuf链表中的某个中间节点删除了, 那就会出错 !!

        //一下子学这么多,肯定缓不过来,很正常,多重复几遍,  计算机网络并不简单  ！！！！！！！！！！！！！

        /**********************************************************************************************************************************/
        关于网卡中使用pbuf的问题 在LwIP移植的时候,
        会进行讲解！！！！！！！！！！！

            同一个以太网帧,
        是怎样区分的 ? ? ? ? ?

                             /**
     *      我们的开发板上设置的IP地址必须是与路由器处于同一子网的,否则路由器不会对
     *  这个IP地址的数据包进行转发
     *      网关就写对应的网关(路由器IP地址)
     *      255.255.255.0 为整个局域网的子网掩码
     */